﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using xplang.run;
using xplang.errors;
using xplang.contexts;
using System.Diagnostics;

namespace xplang.abstractions
{

  [DebuggerDisplay("{_this}: {type}")]
  public class Value
  {
    public Value(ClassRef type)
    {
      this.type = type;
      this.pointer = null;
    }
    public Value(ClassRef classRef, Object pointer)
    {
      this.type = classRef;
      this.pointer = pointer;
    }
    public readonly ClassRef type;
    public Object pointer;
    public Value clone()
    {
      return new Value(type, pointer);
    }

    public Value Get(String attribute)
    {
      ClassDecl decl=type.getClassDecl();
      Variable v = decl.findAttribute(attribute, true);
      return decl.getAttributeValue(this, v);
    }

    public Value Get(Variable attribute)
    {
      ClassDecl decl = type.getClassDecl();
      return decl.getAttributeValue(this, attribute);
    }

    public void Set(String attribute, Value value)
    {
      ClassDecl decl = type.getClassDecl();
      Variable v = decl.findAttribute(attribute, true);
      decl.setAttributeValue(this, v, value);
    }

    public void Set(Variable attribute, Value value)
    {
      ClassDecl decl = type.getClassDecl();
      decl.setAttributeValue(this, attribute, value);
    }
  }

  [DebuggerDisplay("{name}")]
  public abstract class ClassDecl
  {

    public readonly Context context;
    public readonly String name;
    public readonly List<ClassMethod> methods = new List<ClassMethod>();
    public ClassRef[] inheritance;
    public GenericParamDecl[] genericParams;
    public List<Variable> attributes = new List<Variable>();
    public ClassDecl(Context context, String name)
    {
      this.context = context;
      this.name = name;
    }

    public string fullname()
    {
      return (context == null || context.name == null ? "" : context.fullname() + ".") + name;
    }

    public Variable findAttribute(String name, bool inherited)
    {
      Variable v = attributes.SingleOrDefault(i => i.name == name);
      if (v == null && inherited)
      {
        int n = inheritance.Length;
        for (int i = 0; i < n; i++)
        {
          v = inheritance[i].getClassDecl().findAttribute(name, true);
          if (v != null)
            return v;
        }
      }
      return v;
    }

    public abstract object newInstance();
    public abstract Value getAttributeValue(Value _this, Variable variable);
    public abstract void setAttributeValue(Value _this, Variable variable, Value obj);

    public ClassMethod findMethod(string method, ClassRef[] paramTypes, ClassRef[] mappings, bool inherited)
    {
      ClassMethod mtd = methods.SingleOrDefault(me => me.name == method && me.supportTheseParameters(paramTypes, mappings));
      if (mtd == null && inherited)
      {
        int n = inheritance.Length;
        for (int i = 0; i < n; i++)
        {
          mtd = inheritance[i].getClassDecl().
            findMethod(method, paramTypes, inheritance[i].getGenericMappings(), true);
          if (mtd != null)
            return mtd;
        }
      }
      return mtd;
    }

    public bool extends(ClassDecl type)
    {
      if (type == this)
        return true;
      int n = inheritance.Length;
      for (int i = 0; i < n; i++)
        if (inheritance[i].getClassDecl().extends(type))
          return true;
      return false;
    }

    List<ClassRealRef> refs = new List<ClassRealRef>();

    public virtual ClassRef getRef(ClassRef[] genericParams)
    {
      ClassRealRef r = refs.SingleOrDefault(rr => rr.isRef(genericParams));
      if (r == null)
      {
        r = new ClassRealRef(this, genericParams);
        refs.Add(r);
      }
      return r;
    }
  }

  public abstract class NativeClass : ClassDecl
  {
    public NativeClass(Context context, String name)
      : base(context, name)
    {
    }

    public abstract void registerMembers();
  }

  public class CompiledClass : ClassDecl
  {
    public CompiledClass(Context context, String name)
      : base(context, name)
    {
    }

    public override Value getAttributeValue(Value _this, Variable variable)
    {
      throw new NotImplementedException();
    }

    public override void setAttributeValue(Value _this, Variable variable, Value obj)
    {
      throw new NotImplementedException();
    }

    //public override void save(Scope scope, SourceBuilder target)
    //{
    //    target.write(mode.ToString().ToLower(), name);
    //    bool first = true;
    //    foreach (AnyClass c in inheritance)
    //    {
    //        if (first)
    //        {
    //            target.write(": ");
    //            first = false;
    //        }
    //        else
    //            target.write(", ");
    //        c.save(scope, target);
    //    }
    //    target.writeln("{");
    //    first = true;
    //    foreach (AnyClass c in attributes)
    //    {
    //        if (first)
    //            first = false;
    //        else
    //            target.write(";");
    //        c.save(scope, target);
    //    }
    //    foreach (AnyClass c in methods)
    //        c.save(scope, target);
    //    target.writeln("}");
    //}

    //public override void toCSharp(Scope scope, SourceBuilder target)
    //{
    //    target.write("class ", name);
    //    // inheritence
    //    target.writeln("{");
    //    // attributes
    //    // methods
    //    target.writeln("}");
    //}

    //public override void toJavaScript(Scope scope, SourceBuilder target)
    //{
    //    target.write(name, " = new function()");
    //    // inheritence
    //    target.writeln("{");
    //    // attributes
    //    // methods
    //    target.writeln("}");
    //}

    public override object newInstance()
    {
      return new Dictionary<Variable,Value>();
      //initializeObject
      //new XObject();//
      //Scope(scope.global, null, scope.genericMappings, null, null, null);
    }
  }
}
